package red.stu.pconstraint.engine;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import red.stu.pconstraint.ProcessUnit;
import red.stu.pconstraint.dto.mapper.Branch;
import red.stu.pconstraint.dto.mapper.BranchNode;
import red.stu.pconstraint.dto.mapper.block.WhenBlock;
import red.stu.pconstraint.dto.mapper.token.*;
import red.stu.pconstraint.engine.exception.*;
import red.stu.pconstraint.pojo.ExecutionLocation;
import red.stu.pconstraint.pojo.ProcessStorage;

import java.util.LinkedList;
import java.util.List;

/**
 * 工作流引擎
 * @author yihrmc @stu.red
 * @date 2019/11/15 8:58
 */
public class WorkFlowEngine {

    /**
     * 引擎的运行空间
     */
    private WorkingSpace space;

    /**
     * 流程事件监听
     */
    private BaseProcessEventListener processEventListener;

    public WorkFlowEngine(WorkingSpace space, BaseProcessEventListener processEventListener) {
        this.space = space;
        this.processEventListener = processEventListener;
    }

    public WorkingSpace getSpace() {
        return space;
    }

    /**
     * 获取流程处理单元
     * @param processInstanceId 流程实例ID (当创建流程时为null)
     * @param processId 流程模式ID
     * @param executionLocation 当前执行栈
     * @return
     */
    @Nullable
    protected EngineUnit getEngineUnit(@Nullable String processInstanceId, @NotNull String processId, @NotNull ExecutionLocation executionLocation) {
        try {
            if (!processId.equals(space.getProcessId())) {
                // 不是当前引擎负责的流程
                throw new NotExpectedProcessException(processInstanceId, processId);
            }

            if (processInstanceId == null) {
                // 没有实例ID，需要创建新流程实例
                return createProcess(executionLocation);
            }

            // 有实例ID，进行流程处理
            ProcessStorage processData = space.getStorage().getProcessData(processInstanceId);
            if (processData == null) {
                // 无法找到流程实例数据
                throw new ProcessStorageNotFoundException(processInstanceId);
            }

            checkProcess(processData.getState());

            // check Storage Location
            ExecutionLocation currentLocation = processData.getCurrentLocation();
            if (!executionLocation.equalsIgnoreNull(currentLocation)) {
                // 当前调用位置与流程预期位置不一致
                throw new NoSuchLocationException(executionLocation, currentLocation);
            }

            return new EngineUnit(processData);
        } catch (Exception e) {
            processEventListener.onThrowException(e, processInstanceId, this);
        }
        return null;
    }

    /**
     * 挂起一个流程实例
     * @param processInstanceId
     */
    public void suspendProcessInstance(@Nullable String processInstanceId) {
        Short state = space.getStorage().getState(processInstanceId);
        if (!ProcessStorage.STATE_PROCESSING.equals(state)) {
            throw new ChangeStateException("State is not processing");
        }
        space.getStorage().updateState(processInstanceId, ProcessStorage.STATE_SUSPEND);
        processEventListener.onSuspended(processInstanceId, this);
    }

    /**
     * 激活一个挂起的流程实例
     * @param processInstanceId
     */
    public void activateProcessInstance(@Nullable String processInstanceId) {
        Short state = space.getStorage().getState(processInstanceId);
        if (!ProcessStorage.STATE_SUSPEND.equals(state)) {
            throw new ChangeStateException("State is not suspend");
        }
        space.getStorage().updateState(processInstanceId, ProcessStorage.STATE_PROCESSING);
        processEventListener.onSuspendedRecovery(processInstanceId, this);
    }

    /**
     * 创建新的流程实例
     * @param executionLocation
     * @return
     */
    protected EngineUnit createProcess(ExecutionLocation executionLocation) {
        String branchId = executionLocation.getBranchId();
        if (branchId == null) {
            throw new NullPointerException("branchId is null");
        }
        String nodeId = executionLocation.getNodeId();
        if (nodeId == null) {
            throw new NullPointerException("nodeId is null");
        }

        Branch branch = space.getProcessMapper().getBranch(branchId);
        if (branch == null) {
            throw new NoSuchBranchException(space.getProcessId(), branchId);
        }

        if (!Branch.MASTER_ID.equals(branchId)) {
            // 当不是初始主分支时流程实例ID不能为空
            throw new CreateProcessException("Not the MASTER branch: " + branchId);
        }
        BranchNode firstNode = branch.getFirstNode();
        if (firstNode == null) {
            // 当前分支没有任何节点
            throw new CreateProcessException("Branch has no nodes: " + branchId);
        }
        if (!nodeId.equals(firstNode.getId())) {
            // 当前节点不是第一个节点不能创建流程
            throw new CreateProcessException("Not the first node of the branch: " + branchId);
        }

        ProcessStorage processStorage = new ProcessStorage();
        processStorage.setProcessId(space.getProcessId());

        LinkedList<ExecutionLocation> callStack = new LinkedList<>();
        callStack.add(executionLocation);
        processStorage.setCallStack(callStack);
        processStorage.setState(ProcessStorage.STATE_PROCESSING);

        String processInstanceId = space.getStorage().createProcessData(processStorage, space);
        processStorage.setProcessInstanceId(processInstanceId);

        processEventListener.onCreated(processStorage, this);

        return new EngineUnit(processStorage);
    }

    /**
     * 检测流程状态
     */
    private void checkProcess(short state) {
        if (state < ProcessStorage.STATE_PROCESSING) {
            // 流程处于初始状态不能执行
            throw new IllegalProcessStateException("Process not initialized");
        }
        if (state >= ProcessStorage.STATE_SUCCESS) {
            // 流程已处理完成不能重复执行
            throw new IllegalProcessStateException("Process finished");
        }
    }

    /**
     * 严格检测流程状态
     */
    private void strictCheckProcess(short state) {
        if (state == ProcessStorage.STATE_SUSPEND) {
            // 当前流程被挂起，无法执行
            throw new ProcessSuspendException();
        }
        checkProcess(state);
    }

    /**
     * 引擎处理单元
     */
    public class EngineUnit implements ProcessUnit {

        /**
         * 流程实例数据
         */
        private ProcessStorage processData;
        private String processInstanceId;

        public EngineUnit(ProcessStorage processData) {
            this.processData = processData;
            this.processInstanceId = processData.getProcessInstanceId();
        }

        /**
         * 获取当前分支节点
         * @return
         */
        public BranchNode getCurrentBranchNode() {
            ExecutionLocation currentLocation = processData.getCurrentLocation();

            String branchId = currentLocation.getBranchId();
            Branch branch = space.getProcessMapper().getBranch(branchId);

            String nodeId = currentLocation.getNodeId();
            BranchNode currentBranchNode = branch.getBranchNode(nodeId);
            return currentBranchNode;
        }

        /**
         * 切换流程
         * @param callStack 执行栈
         * @return
         */
        private void changeProcess(LinkedList<ExecutionLocation> callStack) {
            space.getStorage().updateProcessCallStack(processInstanceId, callStack);
            processEventListener.onChanged(callStack, processInstanceId, WorkFlowEngine.this);
        }

        /**
         * 运行块表达式
         * @param nodeBlockIndex 节点块索引
         * @param expressionIndex 开始执行表达式索引(为null时执行第一个)
         * @param expressions 表达式组
         */
        public void run(@NotNull Integer nodeBlockIndex, @Nullable Integer expressionIndex, @NotNull List<BlockExpression> expressions) {
            // TODO E start
            BlockExpression[] blockExpressions = expressions.toArray(new BlockExpression[expressions.size()]);
            if (expressionIndex == null) {
                expressionIndex = 0;
            }
            for (int i = expressionIndex, len = blockExpressions.length; i < len; i++) {
                BlockExpression expression = blockExpressions[i];
                if (expression instanceof GotoExpression) {
                    // 调用其他分支
                    GotoExpression gotoExpression = (GotoExpression) expression;
                    String branchId = gotoExpression.getBranchId();
                    if (branchId == null) {
                        // 流程分支不能为空
                        throw new GotoExpressionException("BranchId cannot be empty");
                    }

                    String currentBranchId = processData.getCurrentLocation().getBranchId();
                    if (branchId.equals(currentBranchId)) {
                        // 跳转节点不能为当前自身节点
                        throw new GotoExpressionException("The calling node cannot be the current node");
                    }

                    Branch branch = space.getProcessMapper().getBranch(branchId);
                    if (branch == null) {
                        throw new NoSuchBranchException(space.getProcessId(), branchId);
                    }

                    String nodeId = gotoExpression.getNodeId();
                    BranchNode branchNode;
                    if (nodeId == null) {
                        // 获取分支的第一个节点
                        branchNode = branch.getFirstNode();
                        nodeId = branchNode.getId();
                    } else {
                        branchNode = branch.getBranchNode(nodeId);
                    }
                    if (branchNode == null) {
                        throw new NoSuchBranchNodeException(space.getProcessId(), branchId, nodeId);
                    }

                    // 保存当前分支调用位置
                    ExecutionLocation currentLocation = processData.getCurrentLocation();
                    currentLocation.setNodeBlockIndex(nodeBlockIndex);
                    processData.updateCurrentLocation(currentLocation);
                    // 添加其他分支调用
                    LinkedList<ExecutionLocation> callStack = processData.getCallStack();
                    ExecutionLocation otherLocation = new ExecutionLocation()
                            .setBranchId(branchId)
                            .setNodeId(branchNode.getId());
                    callStack.add(otherLocation);
                    this.changeProcess(processData.getCallStack());
                    return;

                } else if (expression instanceof CloseExpression) {
                    CloseExpression closeExpression = (CloseExpression) expression;
                    String name = closeExpression.getName();
                    this.close(ProcessStorage.STATE_FAIL, name);
                    return;

                } else if (expression instanceof PassExpression) {
                    PassExpression passExpression = (PassExpression) expression;
                    String name = passExpression.getName();
                    this.close(ProcessStorage.STATE_SUCCESS, name);
                    return;

                } else if (expression instanceof BackExpression) {
                    BackExpression backExpression = (BackExpression) expression;
                    String branchId = backExpression.getBranchId();
                    if (branchId == null) {
                        // 默认当前分支
                        branchId = processData.getCurrentLocation().getBranchId();
                    }
                    Branch branch = space.getProcessMapper().getBranch(branchId);
                    if (branch == null) {
                        // 找不到分支
                        throw new NoSuchBranchException(space.getProcessId(), branchId);
                    }

                    String nodeId = backExpression.getNodeId();
                    if (nodeId == null) {
                        // 退回节点id不能为空
                        throw new BackExpressionExpression("NodeId cannot be empty");
                    }

                    // 搜索退回目标
                    LinkedList<ExecutionLocation> historyCallStack = getHistoryCallStack(branchId, nodeId);
                    if (historyCallStack == null) {
                        // 找不到退回目标
                        throw new RuntimeException("Target not found");
                    }
                    this.changeProcess(historyCallStack);
                    return;
                }
            }


        }

        /**
         * 获取历史的节点执行栈
         * @param branchId 搜索的分支ID
         * @param nodeId 搜索的节点ID
         * @return
         */
        @Nullable
        private LinkedList<ExecutionLocation> getHistoryCallStack(String branchId, String nodeId) {
            Branch branch = space.getProcessMapper().getBranch(branchId);
            LinkedList<ExecutionLocation> callStack = processData.getCallStack();
            ExecutionLocation[] callStackArray = callStack.toArray(new ExecutionLocation[callStack.size()]);
            for (int j = callStackArray.length - 1; j >= 0; j--) {
                ExecutionLocation filterLocation = callStackArray[j];
                if (branchId.equals(filterLocation.getBranchId())) {
                    // 搜索指定目标分支节点之前的节点
                    String filterNodeId = filterLocation.getNodeId();
                    BranchNode filterBranchNode = branch.getBranchNode(filterNodeId);
                    while (filterBranchNode != null) {
                        if (nodeId.equals(filterBranchNode.getId())) {
                            // 找到退回目标分支与节点ID
                            LinkedList<ExecutionLocation> historyCallStack = new LinkedList<>();
                            for (int i = 0; i < j; i++) {
                                historyCallStack.add(callStackArray[i]);
                            }
                            ExecutionLocation newLocation = new ExecutionLocation()
                                    .setBranchId(branchId)
                                    .setNodeId(nodeId);
                            historyCallStack.add(newLocation);
                            return historyCallStack;
                        }
                        filterBranchNode = filterBranchNode.getPreviousBranchNode();
                    }
                }
            }
            return null;
        }

        @Override
        public ProcessStorage getProcessData() {
            return processData;
        }

        @Override
        public void next() {
            strictCheckProcess(processData.getState());

            BranchNode currentBranchNode = getCurrentBranchNode();
            if (currentBranchNode.isLast()) {
                processData.getCallStack().removeLast();
                ExecutionLocation nextLocation = processData.getCurrentLocation();
                if (nextLocation == null) {
                    // 执行完毕
                    this.close(ProcessStorage.STATE_SUCCESS);
                    return;
                }

                // 当前在嵌套中的最后一个节点，直接继续下一个节点
                this.next();

            } else {
                BranchNode nextBranchNode = currentBranchNode.getNextBranchNode();

                ExecutionLocation currentLocation = processData.getCurrentLocation();
                currentLocation.setNodeId(nextBranchNode.getId());
                processData.updateCurrentLocation(currentLocation);

                this.changeProcess(processData.getCallStack());
            }
        }

        @Override
        public void changeBranch(@NotNull String stateName) {
            strictCheckProcess(processData.getState());

            BranchNode currentBranchNode = getCurrentBranchNode();
            WhenBlock whenBlock = currentBranchNode.getWhenBlock(stateName);
            if (whenBlock == null) {
                throw new NoSuchWhenBlockException("stateName: " + stateName);
            }
            List<BlockExpression> expressions = whenBlock.getExpressions();
            if (expressions == null || expressions.isEmpty()) {
                // 节点块不能为空
                throw new ChangeBranchException("Node block cannot be empty. stateName: " + stateName);
            }

            Integer nodeBlockIndex = whenBlock.getIndex();
            run(nodeBlockIndex, null, expressions);
        }

        @Override
        public void close(@NotNull Short state) {
            close(state, null);
        }

        @Override
        public void close(@NotNull Short state, @Nullable String closeName) {
            strictCheckProcess(processData.getState());

            space.getStorage().updateStateAndCallStack(processInstanceId, state, null);

            if (ProcessStorage.STATE_SUCCESS.equals(state)) {
                processEventListener.onSuccess(processInstanceId, WorkFlowEngine.this);

            } else if (ProcessStorage.STATE_FAIL.equals(state)) {
                processEventListener.onFail(processInstanceId, WorkFlowEngine.this);

            }
            processEventListener.onComplete(processInstanceId, WorkFlowEngine.this);
        }

        @Override
        public void doTimer(@NotNull String timerName, long outTime) {

        }

        @Override
        public void doTimer(@NotNull String timerName, long outTime, boolean autoClear) {

        }

        @Override
        public void clearTimer(@NotNull String timerName) {

        }

    }

}
